package org.zendesk.client.v2; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.ning.http.client.AsyncCompletionHandler; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder; import com.ning.http.client.ListenableFuture; import com.ning.http.client.Realm; import com.ning.http.client.Request; import com.ning.http.client.RequestBuilder; import com.ning.http.client.Response; import com.ning.http.client.multipart.FilePart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zendesk.client.v2.model.AgentRole; import org.zendesk.client.v2.model.Attachment; import org.zendesk.client.v2.model.Audit; import org.zendesk.client.v2.model.Automation; import org.zendesk.client.v2.model.Brand; import org.zendesk.client.v2.model.Comment; import org.zendesk.client.v2.model.Field; import org.zendesk.client.v2.model.Forum; import org.zendesk.client.v2.model.Group; import org.zendesk.client.v2.model.GroupMembership; import org.zendesk.client.v2.model.Identity; import org.zendesk.client.v2.model.JobStatus; import org.zendesk.client.v2.model.Macro; import org.zendesk.client.v2.model.Metric; import org.zendesk.client.v2.model.Organization; import org.zendesk.client.v2.model.OrganizationField; import org.zendesk.client.v2.model.OrganizationMembership; import org.zendesk.client.v2.model.SatisfactionRating; import org.zendesk.client.v2.model.SearchResultEntity; import org.zendesk.client.v2.model.Status; import org.zendesk.client.v2.model.SuspendedTicket; import org.zendesk.client.v2.model.Ticket; import org.zendesk.client.v2.model.TicketForm; import org.zendesk.client.v2.model.TicketResult; import org.zendesk.client.v2.model.Topic; import org.zendesk.client.v2.model.Trigger; import org.zendesk.client.v2.model.TwitterMonitor; import org.zendesk.client.v2.model.User; import org.zendesk.client.v2.model.UserField; import org.zendesk.client.v2.model.hc.Article; import org.zendesk.client.v2.model.hc.ArticleAttachments; import org.zendesk.client.v2.model.hc.Category; import org.zendesk.client.v2.model.hc.Section; import org.zendesk.client.v2.model.hc.Subscription; import org.zendesk.client.v2.model.hc.Translation; import org.zendesk.client.v2.model.schedules.Holiday; import org.zendesk.client.v2.model.schedules.Schedule; import org.zendesk.client.v2.model.targets.BasecampTarget; import org.zendesk.client.v2.model.targets.CampfireTarget; import org.zendesk.client.v2.model.targets.EmailTarget; import org.zendesk.client.v2.model.targets.PivotalTarget; import org.zendesk.client.v2.model.targets.Target; import org.zendesk.client.v2.model.targets.TwitterTarget; import org.zendesk.client.v2.model.targets.UrlTarget; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; /** * @author stephenc * @since 04/04/2013 13:08 */ public class Zendesk implements Closeable { private static final String JSON = "application/json; charset=UTF-8"; private final boolean closeClient; private final AsyncHttpClient client; private final Realm realm; private final String url; private final String oauthToken; private final ObjectMapper mapper; private final Logger logger; private boolean closed = false; private static final Map<String, Class<? extends SearchResultEntity>> searchResultTypes = searchResultTypes(); private static final Map<String, Class<? extends Target>> targetTypes = targetTypes(); private static Map<String, Class<? extends SearchResultEntity>> searchResultTypes() { Map<String, Class<? extends SearchResultEntity>> result = new HashMap<String, Class<? extends SearchResultEntity>>(); result.put("ticket", Ticket.class); result.put("user", User.class); result.put("group", Group.class); result.put("organization", Organization.class); result.put("topic", Topic.class); result.put("article", Article.class); return Collections.unmodifiableMap(result); } private static Map<String, Class<? extends Target>> targetTypes() { Map<String, Class<? extends Target>> result = new HashMap<String, Class<? extends Target>>(); result.put("url_target", UrlTarget.class); result.put("email_target",EmailTarget.class); result.put("basecamp_target", BasecampTarget.class); result.put("campfire_target", CampfireTarget.class); result.put("pivotal_target", PivotalTarget.class); result.put("twitter_target", TwitterTarget.class); // TODO: Implement other Target types //result.put("clickatell_target", ClickatellTarget.class); //result.put("flowdock_target", FlowdockTarget.class); //result.put("get_satisfaction_target", GetSatisfactionTarget.class); //result.put("yammer_target", YammerTarget.class); return Collections.unmodifiableMap(result); } private Zendesk(AsyncHttpClient client, String url, String username, String password) { this.logger = LoggerFactory.getLogger(Zendesk.class); this.closeClient = client == null; this.oauthToken = null; this.client = client == null ? new AsyncHttpClient() : client; this.url = url.endsWith("/") ? url + "api/v2" : url + "/api/v2"; if (username != null) { this.realm = new Realm.RealmBuilder() .setScheme(Realm.AuthScheme.BASIC) .setPrincipal(username) .setPassword(password) .setUsePreemptiveAuth(true) .build(); } else { if (password != null) { throw new IllegalStateException("Cannot specify token or password without specifying username"); } this.realm = null; } this.mapper = createMapper(); } private Zendesk(AsyncHttpClient client, String url, String oauthToken) { this.logger = LoggerFactory.getLogger(Zendesk.class); this.closeClient = client == null; this.realm = null; this.client = client == null ? new AsyncHttpClient() : client; this.url = url.endsWith("/") ? url + "api/v2" : url + "/api/v2"; if (oauthToken != null) { this.oauthToken = oauthToken; } else { throw new IllegalStateException("Cannot specify token or password without specifying username"); } this.mapper = createMapper(); } ////////////////////////////////////////////////////////////////////// // Closeable interface methods ////////////////////////////////////////////////////////////////////// public boolean isClosed() { return closed || client.isClosed(); } public void close() { if (closeClient && !client.isClosed()) { client.close(); } closed = true; } ////////////////////////////////////////////////////////////////////// // Action methods ////////////////////////////////////////////////////////////////////// public <T> JobStatus<T> getJobStatus(JobStatus<T> status) { return complete(getJobStatusAsync(status)); } public <T> ListenableFuture<JobStatus<T>> getJobStatusAsync(JobStatus<T> status) { return submit(req("GET", tmpl("/job_statuses/{id}.json").set("id", status.getId())), handleJobStatus(status.getResultsClass())); } public List<JobStatus<HashMap<String, Object>>> getJobStatuses(List<JobStatus> statuses) { return complete(getJobStatusesAsync(statuses)); } public ListenableFuture<List<JobStatus<HashMap<String, Object>>>> getJobStatusesAsync(List<JobStatus> statuses) { List<String> ids = new ArrayList<String>(statuses.size()); for (JobStatus status : statuses) { ids.add(status.getId()); } Class<JobStatus<HashMap<String, Object>>> clazz = (Class<JobStatus<HashMap<String, Object>>>)(Object)JobStatus.class; return submit(req("GET", tmpl("/job_statuses/show_many.json{?ids}").set("ids", ids)), handleList(clazz, "job_statuses")); } public List<Brand> getBrands(){ return complete(submit(req("GET", cnst("/brands.json")), handleList(Brand.class, "brands"))); } public TicketForm getTicketForm(long id) { return complete(submit(req("GET", tmpl("/ticket_forms/{id}.json").set("id", id)), handle(TicketForm.class, "ticket_form"))); } public List<TicketForm> getTicketForms() { return complete(submit(req("GET", cnst("/ticket_forms.json")), handleList(TicketForm.class, "ticket_forms"))); } public Ticket getTicket(long id) { return complete(submit(req("GET", tmpl("/tickets/{id}.json").set("id", id)), handle(Ticket.class, "ticket"))); } public List<Ticket> getTicketIncidents(long id) { return complete(submit(req("GET", tmpl("/tickets/{id}/incidents.json").set("id", id)), handleList(Ticket.class, "tickets"))); } public List<User> getTicketCollaborators(long id) { return complete(submit(req("GET", tmpl("/tickets/{id}/collaborators.json").set("id", id)), handleList(User.class, "users"))); } public void deleteTicket(Ticket ticket) { checkHasId(ticket); deleteTicket(ticket.getId()); } public void deleteTicket(long id) { complete(submit(req("DELETE", tmpl("/tickets/{id}.json").set("id", id)), handleStatus())); } public Ticket createTicket(Ticket ticket) { return complete(submit(req("POST", cnst("/tickets.json"), JSON, json(Collections.singletonMap("ticket", ticket))), handle(Ticket.class, "ticket"))); } public JobStatus<Ticket> createTickets(Ticket... tickets) { return createTickets(Arrays.asList(tickets)); } public JobStatus<Ticket> createTickets(List<Ticket> tickets) { return complete(createTicketsAsync(tickets)); } public ListenableFuture<JobStatus<Ticket>> createTicketsAsync(List<Ticket> tickets) { return submit(req("POST", cnst("/tickets/create_many.json"), JSON, json( Collections.singletonMap("tickets", tickets))), handleJobStatus(Ticket.class)); } public Ticket updateTicket(Ticket ticket) { checkHasId(ticket); return complete(submit(req("PUT", tmpl("/tickets/{id}.json").set("id", ticket.getId()), JSON, json(Collections.singletonMap("ticket", ticket))), handle(Ticket.class, "ticket"))); } public void markTicketAsSpam(Ticket ticket) { checkHasId(ticket); markTicketAsSpam(ticket.getId()); } public void markTicketAsSpam(long id) { complete(submit(req("PUT", tmpl("/tickets/{id}/mark_as_spam.json").set("id", id)), handleStatus())); } public void deleteTickets(long id, long... ids) { complete(submit(req("DELETE", tmpl("/tickets/destroy_many.json{?ids}").set("ids", idArray(id, ids))), handleStatus())); } public Iterable<Ticket> getTickets() { return new PagedIterable<Ticket>(cnst("/tickets.json"), handleList(Ticket.class, "tickets")); } /** * @deprecated This API is no longer available from the vendor. Use the {@link #getTicketsFromSearch(String)} method instead * @param ticketStatus * @return */ @Deprecated public Iterable<Ticket> getTicketsByStatus(Status... ticketStatus) { return new PagedIterable<Ticket>(tmpl("/tickets.json{?status}").set("status", statusArray(ticketStatus)), handleList(Ticket.class, "tickets")); } public Iterable<Ticket> getTicketsByExternalId(String externalId, boolean includeArchived) { Iterable<Ticket> results = new PagedIterable<Ticket>(tmpl("/tickets.json{?external_id}").set("external_id", externalId), handleList(Ticket.class, "tickets")); if (!includeArchived || results.iterator().hasNext()) { return results; } return new PagedIterable<Ticket>(tmpl("/search.json{?query}{&type}").set("query", "external_id:" + externalId).set("type", "ticket"), handleList(Ticket.class, "results")); } public Iterable<Ticket> getTicketsByExternalId(String externalId) { return getTicketsByExternalId(externalId, false); } public Iterable<Ticket> getTicketsFromSearch(String searchTerm) { return new PagedIterable<Ticket>(tmpl("/search.json{?query}").set("query", searchTerm + "+type:ticket"), handleList(Ticket.class, "results")); } public Iterable<Article> getArticleFromSearch(String searchTerm) { return new PagedIterable<Article>(tmpl("/help_center/articles/search.json{?query}").set("query", searchTerm), handleList(Article.class, "results")); } public Iterable<Article> getArticleFromSearch(String searchTerm, Long sectionId) { return new PagedIterable<Article>(tmpl("/help_center/articles/search.json{?section,query}") .set("query", searchTerm).set("section", sectionId), handleList(Article.class, "results")); } public List<ArticleAttachments> getAttachmentsFromArticle(Long articleID) { return complete(submit(req("GET", tmpl("/help_center/articles/{id}/attachments.json").set("id", articleID)), handleArticleAttachmentsList("article_attachments"))); } public List<Ticket> getTickets(long id, long... ids) { return complete(submit(req("GET", tmpl("/tickets/show_many.json{?ids}").set("ids", idArray(id, ids))), handleList(Ticket.class, "tickets"))); } public Iterable<Ticket> getRecentTickets() { return new PagedIterable<Ticket>(cnst("/tickets/recent.json"), handleList(Ticket.class, "tickets")); } public Iterable<Ticket> getTicketsIncrementally(Date startTime) { return new PagedIterable<Ticket>( tmpl("/incremental/tickets.json{?start_time}").set("start_time", msToSeconds(startTime.getTime())), handleIncrementalList(Ticket.class, "tickets")); } public Iterable<Ticket> getTicketsIncrementally(Date startTime, Date endTime) { return new PagedIterable<Ticket>( tmpl("/incremental/tickets.json{?start_time,end_time}") .set("start_time", msToSeconds(startTime.getTime())) .set("end_time", msToSeconds(endTime.getTime())), handleIncrementalList(Ticket.class, "tickets")); } public Iterable<Ticket> getOrganizationTickets(long organizationId) { return new PagedIterable<Ticket>( tmpl("/organizations/{organizationId}/tickets.json").set("organizationId", organizationId), handleList(Ticket.class, "tickets")); } public Iterable<Ticket> getUserRequestedTickets(long userId) { return new PagedIterable<Ticket>(tmpl("/users/{userId}/tickets/requested.json").set("userId", userId), handleList(Ticket.class, "tickets")); } public Iterable<Ticket> getUserCCDTickets(long userId) { return new PagedIterable<Ticket>(tmpl("/users/{userId}/tickets/ccd.json").set("userId", userId), handleList(Ticket.class, "tickets")); } public Iterable<Metric> getTicketMetrics() { return new PagedIterable<Metric>(cnst("/ticket_metrics.json"), handleList(Metric.class, "ticket_metrics")); } public Metric getTicketMetricByTicket(long id) { return complete(submit(req("GET", tmpl("/tickets/{ticketId}/metrics.json").set("ticketId", id)), handle(Metric.class, "ticket_metric"))); } public Metric getTicketMetric(long id) { return complete(submit(req("GET", tmpl("/ticket_metrics/{ticketMetricId}.json").set("ticketMetricId", id)), handle(Metric.class, "ticket_metric"))); } public Iterable<Audit> getTicketAudits(Ticket ticket) { checkHasId(ticket); return getTicketAudits(ticket.getId()); } public Iterable<Audit> getTicketAudits(Long id) { return new PagedIterable<Audit>(tmpl("/tickets/{ticketId}/audits.json").set("ticketId", id), handleList(Audit.class, "audits")); } public Audit getTicketAudit(Ticket ticket, Audit audit) { checkHasId(audit); return getTicketAudit(ticket, audit.getId()); } public Audit getTicketAudit(Ticket ticket, long id) { checkHasId(ticket); return getTicketAudit(ticket.getId(), id); } public Audit getTicketAudit(long ticketId, long auditId) { return complete(submit(req("GET", tmpl("/tickets/{ticketId}/audits/{auditId}.json").set("ticketId", ticketId) .set("auditId", auditId)), handle(Audit.class, "audit"))); } public void trustTicketAudit(Ticket ticket, Audit audit) { checkHasId(audit); trustTicketAudit(ticket, audit.getId()); } public void trustTicketAudit(Ticket ticket, long id) { checkHasId(ticket); trustTicketAudit(ticket.getId(), id); } public void trustTicketAudit(long ticketId, long auditId) { complete(submit(req("PUT", tmpl("/tickets/{ticketId}/audits/{auditId}/trust.json").set("ticketId", ticketId) .set("auditId", auditId)), handleStatus())); } public void makePrivateTicketAudit(Ticket ticket, Audit audit) { checkHasId(audit); makePrivateTicketAudit(ticket, audit.getId()); } public void makePrivateTicketAudit(Ticket ticket, long id) { checkHasId(ticket); makePrivateTicketAudit(ticket.getId(), id); } public void makePrivateTicketAudit(long ticketId, long auditId) { complete(submit(req("PUT", tmpl("/tickets/{ticketId}/audits/{auditId}/make_private.json").set("ticketId", ticketId) .set("auditId", auditId)), handleStatus())); } public List<Field> getTicketFields() { return complete(submit(req("GET", cnst("/ticket_fields.json")), handleList(Field.class, "ticket_fields"))); } public Field getTicketField(long id) { return complete(submit(req("GET", tmpl("/ticket_fields/{id}.json").set("id", id)), handle(Field.class, "ticket_field"))); } public Field createTicketField(Field field) { return complete(submit(req("POST", cnst("/ticket_fields.json"), JSON, json( Collections.singletonMap("ticket_field", field))), handle(Field.class, "ticket_field"))); } public Field updateTicketField(Field field) { checkHasId(field); return complete(submit(req("PUT", tmpl("/ticket_fields/{id}.json").set("id", field.getId()), JSON, json(Collections.singletonMap("ticket_field", field))), handle(Field.class, "ticket_field"))); } public void deleteTicketField(Field field) { checkHasId(field); deleteTicket(field.getId()); } public void deleteTicketField(long id) { complete(submit(req("DELETE", tmpl("/ticket_fields/{id}.json").set("id", id)), handleStatus())); } public Iterable<SuspendedTicket> getSuspendedTickets() { return new PagedIterable<SuspendedTicket>(cnst("/suspended_tickets.json"), handleList(SuspendedTicket.class, "suspended_tickets")); } public void deleteSuspendedTicket(SuspendedTicket ticket) { checkHasId(ticket); deleteSuspendedTicket(ticket.getId()); } public void deleteSuspendedTicket(long id) { complete(submit(req("DELETE", tmpl("/suspended_tickets/{id}.json").set("id", id)), handleStatus())); } public Attachment.Upload createUpload(String fileName, byte[] content) { return createUpload(null, fileName, "application/binary", content); } public Attachment.Upload createUpload(String fileName, String contentType, byte[] content) { return createUpload(null, fileName, contentType, content); } public Attachment.Upload createUpload(String token, String fileName, String contentType, byte[] content) { TemplateUri uri = tmpl("/uploads.json{?filename,token}").set("filename", fileName); if (token != null) { uri.set("token", token); } return complete( submit(req("POST", uri, contentType, content), handle(Attachment.Upload.class, "upload"))); } public void associateAttachmentsToArticle(String idArticle, List<Attachment> attachments) { TemplateUri uri = tmpl("/help_center/articles/{article_id}/bulk_attachments.json").set("article_id", idArticle); List<Long> attachmentsIds = new ArrayList<Long>(); for(Attachment item : attachments){ attachmentsIds.add(item.getId()); } complete(submit(req("POST", uri, JSON, json(Collections.singletonMap("attachment_ids", attachmentsIds))), handleStatus())); } public ArticleAttachments createUploadArticle(long articleId, File file) throws IOException { BoundRequestBuilder builder = client.preparePost(tmpl("/help_center/articles/{id}/attachments.json").set("id", articleId).toString()); if (realm != null) { builder.setRealm(realm); } else { builder.addHeader("Authorization", "Bearer " + oauthToken); } builder.setHeader("Content-Type", "multipart/form-data"); builder.addBodyPart( new FilePart("file", file, "application/octet-stream", Charset.forName("UTF-8"), file.getName())); final Request req = builder.build(); return complete(submit(req, handle(ArticleAttachments.class, "article_attachment"))); } public void deleteUpload(Attachment.Upload upload) { checkHasToken(upload); deleteUpload(upload.getToken()); } public void deleteUpload(String token) { complete(submit(req("DELETE", tmpl("/uploads/{token}.json").set("token", token)), handleStatus())); } public Attachment getAttachment(Attachment attachment) { checkHasId(attachment); return getAttachment(attachment.getId()); } public Attachment getAttachment(long id) { return complete(submit(req("GET", tmpl("/attachments/{id}.json").set("id", id)), handle(Attachment.class, "attachment"))); } public void deleteAttachment(Attachment attachment) { checkHasId(attachment); deleteAttachment(attachment.getId()); } public void deleteAttachment(long id) { complete(submit(req("DELETE", tmpl("/attachments/{id}.json").set("id", id)), handleStatus())); } public Iterable<Target> getTargets() { return new PagedIterable<Target>(cnst("/targets.json"), handleTargetList("targets")); } public Target getTarget(long id) { return complete(submit(req("GET", tmpl("/targets/{id}.json").set("id", id)), handle(Target.class, "target"))); } public Target createTarget(Target target) { return complete(submit(req("POST", cnst("/targets.json"), JSON, json(Collections.singletonMap("target", target))), handle(Target.class, "target"))); } public void deleteTarget(long targetId) { complete(submit(req("DELETE", tmpl("/targets/{id}.json").set("id", targetId)), handleStatus())); } public Iterable<Trigger> getTriggers() { return new PagedIterable<Trigger>(cnst("/triggers.json"), handleList(Trigger.class, "triggers")); } public Trigger getTrigger(long id) { return complete(submit(req("GET", tmpl("/triggers/{id}.json").set("id", id)), handle(Trigger.class, "trigger"))); } public Trigger createTrigger(Trigger trigger) { return complete(submit(req("POST", cnst("/triggers.json"), JSON, json(Collections.singletonMap("trigger", trigger))), handle(Trigger.class, "trigger"))); } public Trigger updateTrigger(Long triggerId, Trigger trigger) { return complete(submit(req("PUT", tmpl("/triggers/{id}.json").set("id", triggerId), JSON, json(Collections.singletonMap("trigger", trigger))), handle(Trigger.class, "trigger"))); } public void deleteTrigger(long triggerId) { complete(submit(req("DELETE", tmpl("/triggers/{id}.json").set("id", triggerId)), handleStatus())); } // Automations public Iterable<Automation> getAutomations() { return new PagedIterable<Automation>(cnst("/automations.json"), handleList(Automation.class, "automations")); } public Automation getAutomation(long id) { return complete(submit(req("GET", tmpl("/automations/{id}.json").set("id", id)), handle(Automation.class, "automation"))); } public Automation createAutomation(Automation automation) { return complete(submit( req("POST", cnst("/automations.json"), JSON, json(Collections.singletonMap("automation", automation))), handle(Automation.class, "automation"))); } public Automation updateAutomation(Long automationId, Automation automation) { return complete(submit( req("PUT", tmpl("/automations/{id}.json").set("id", automationId), JSON, json(Collections.singletonMap("automation", automation))), handle(Automation.class, "automation"))); } public void deleteAutomation(long automationId) { complete(submit(req("DELETE", tmpl("/automations/{id}.json").set("id", automationId)), handleStatus())); } public Iterable<TwitterMonitor> getTwitterMonitors() { return new PagedIterable<TwitterMonitor>(cnst("/channels/twitter/monitored_twitter_handles.json"), handleList(TwitterMonitor.class, "monitored_twitter_handles")); } public Iterable<User> getUsers() { return new PagedIterable<User>(cnst("/users.json"), handleList(User.class, "users")); } public Iterable<User> getUsersByRole(String role, String... roles) { // Going to have to build this URI manually, because the RFC6570 template spec doesn't support // variables like ?role[]=...role[]=..., which is what Zendesk requires. // See https://developer.zendesk.com/rest_api/docs/core/users#filters final StringBuilder uriBuilder = new StringBuilder("/users.json"); if (roles.length == 0) { uriBuilder.append("?role=").append(encodeUrl(role)); } else { uriBuilder.append("?role[]=").append(encodeUrl(role)); } for (final String curRole : roles) { uriBuilder.append("&role[]=").append(encodeUrl(curRole)); } return new PagedIterable<User>(cnst(uriBuilder.toString()), handleList(User.class, "users")); } public Iterable<User> getUsersIncrementally(Date startTime) { return new PagedIterable<User>( tmpl("/incremental/users.json{?start_time}").set("start_time", msToSeconds(startTime.getTime())), handleIncrementalList(User.class, "users")); } public Iterable<User> getGroupUsers(long id) { return new PagedIterable<User>(tmpl("/groups/{id}/users.json").set("id", id), handleList(User.class, "users")); } public Iterable<User> getOrganizationUsers(long id) { return new PagedIterable<User>(tmpl("/organizations/{id}/users.json").set("id", id), handleList(User.class, "users")); } public User getUser(long id) { return complete(submit(req("GET", tmpl("/users/{id}.json").set("id", id)), handle(User.class, "user"))); } public User getAuthenticatedUser() { return complete(submit(req("GET", cnst("/users/me.json")), handle(User.class, "user"))); } public Iterable<UserField> getUserFields() { return complete(submit(req("GET", cnst("/user_fields.json")), handleList(UserField.class, "user_fields"))); } public User createUser(User user) { return complete(submit(req("POST", cnst("/users.json"), JSON, json( Collections.singletonMap("user", user))), handle(User.class, "user"))); } public JobStatus<User> createUsers(User... users) { return createUsers(Arrays.asList(users)); } public JobStatus<User> createUsers(List<User> users) { return complete(createUsersAsync(users)); } public User createOrUpdateUser(User user) { return complete(submit(req("POST", cnst("/users/create_or_update.json"), JSON, json( Collections.singletonMap("user", user))), handle(User.class, "user"))); } public ListenableFuture<JobStatus<User>> createUsersAsync(List<User> users) { return submit(req("POST", cnst("/users/create_many.json"), JSON, json( Collections.singletonMap("users", users))), handleJobStatus(User.class)); } public User updateUser(User user) { checkHasId(user); return complete(submit(req("PUT", tmpl("/users/{id}.json").set("id", user.getId()), JSON, json( Collections.singletonMap("user", user))), handle(User.class, "user"))); } public void deleteUser(User user) { checkHasId(user); deleteUser(user.getId()); } public void deleteUser(long id) { complete(submit(req("DELETE", tmpl("/users/{id}.json").set("id", id)), handleStatus())); } public Iterable<User> lookupUserByEmail(String email) { return new PagedIterable<User>(tmpl("/users/search.json{?query}").set("query", email), handleList(User.class, "users")); } public Iterable<User> lookupUserByExternalId(String externalId) { return new PagedIterable<User>(tmpl("/users/search.json{?external_id}").set("external_id", externalId), handleList(User.class, "users")); } public User getCurrentUser() { return complete(submit(req("GET", cnst("/users/me.json")), handle(User.class, "user"))); } public void resetUserPassword(User user, String password) { checkHasId(user); resetUserPassword(user.getId(), password); } public void resetUserPassword(long id, String password) { complete(submit(req("POST", tmpl("/users/{id}/password.json").set("id", id), JSON, json(Collections.singletonMap("password", password))), handleStatus())); } public void changeUserPassword(User user, String oldPassword, String newPassword) { checkHasId(user); Map<String, String> req = new HashMap<String, String>(); req.put("previous_password", oldPassword); req.put("password", newPassword); complete(submit(req("PUT", tmpl("/users/{id}/password.json").set("id", user.getId()), JSON, json(req)), handleStatus())); } public List<Identity> getUserIdentities(User user) { checkHasId(user); return getUserIdentities(user.getId()); } public List<Identity> getUserIdentities(long userId) { return complete(submit(req("GET", tmpl("/users/{id}/identities.json").set("id", userId)), handleList(Identity.class, "identities"))); } public Identity getUserIdentity(User user, Identity identity) { checkHasId(identity); return getUserIdentity(user, identity.getId()); } public Identity getUserIdentity(User user, long identityId) { checkHasId(user); return getUserIdentity(user.getId(), identityId); } public Identity getUserIdentity(long userId, long identityId) { return complete(submit(req("GET", tmpl("/users/{userId}/identities/{identityId}.json").set("userId", userId) .set("identityId", identityId)), handle( Identity.class, "identity"))); } public List<Identity> setUserPrimaryIdentity(User user, Identity identity) { checkHasId(identity); return setUserPrimaryIdentity(user, identity.getId()); } public List<Identity> setUserPrimaryIdentity(User user, long identityId) { checkHasId(user); return setUserPrimaryIdentity(user.getId(), identityId); } public List<Identity> setUserPrimaryIdentity(long userId, long identityId) { return complete(submit(req("PUT", tmpl("/users/{userId}/identities/{identityId}/make_primary.json").set("userId", userId) .set("identityId", identityId), JSON, null), handleList(Identity.class, "identities"))); } public Identity verifyUserIdentity(User user, Identity identity) { checkHasId(identity); return verifyUserIdentity(user, identity.getId()); } public Identity verifyUserIdentity(User user, long identityId) { checkHasId(user); return verifyUserIdentity(user.getId(), identityId); } public Identity verifyUserIdentity(long userId, long identityId) { return complete(submit(req("PUT", tmpl("/users/{userId}/identities/{identityId}/verify.json") .set("userId", userId) .set("identityId", identityId), JSON, null), handle(Identity.class, "identity"))); } public Identity requestVerifyUserIdentity(User user, Identity identity) { checkHasId(identity); return requestVerifyUserIdentity(user, identity.getId()); } public Identity requestVerifyUserIdentity(User user, long identityId) { checkHasId(user); return requestVerifyUserIdentity(user.getId(), identityId); } public Identity requestVerifyUserIdentity(long userId, long identityId) { return complete(submit(req("PUT", tmpl("/users/{userId}/identities/{identityId}/request_verification.json") .set("userId", userId) .set("identityId", identityId), JSON, null), handle(Identity.class, "identity"))); } public Identity updateUserIdentity(long userId, Identity identity) { checkHasId(identity); return complete(submit(req("PUT", tmpl("/users/{userId}/identities/{identityId}.json") .set("userId", userId) .set("identityId", identity.getId()), JSON, null), handle(Identity.class, "identity"))); } public Identity updateUserIdentity(User user, Identity identity) { checkHasId(user); return updateUserIdentity(user.getId(), identity); } public void deleteUserIdentity(User user, Identity identity) { checkHasId(identity); deleteUserIdentity(user, identity.getId()); } public void deleteUserIdentity(User user, long identityId) { checkHasId(user); deleteUserIdentity(user.getId(), identityId); } public void deleteUserIdentity(long userId, long identityId) { complete(submit(req("DELETE", tmpl("/users/{userId}/identities/{identityId}.json") .set("userId", userId) .set("identityId", identityId) ), handleStatus())); } public Identity createUserIdentity(long userId, Identity identity) { return complete(submit(req("POST", tmpl("/users/{userId}/identities.json").set("userId", userId), JSON, json(Collections.singletonMap("identity", identity))), handle(Identity.class, "identity"))); } public Identity createUserIdentity(User user, Identity identity) { return complete(submit(req("POST", tmpl("/users/{userId}/identities.json").set("userId", user.getId()), JSON, json(Collections.singletonMap("identity", identity))), handle(Identity.class, "identity"))); } public Iterable<AgentRole> getCustomAgentRoles() { return new PagedIterable<AgentRole>(cnst("/custom_roles.json"), handleList(AgentRole.class, "custom_roles")); } public Iterable<org.zendesk.client.v2.model.Request> getRequests() { return new PagedIterable<org.zendesk.client.v2.model.Request>(cnst("/requests.json"), handleList(org.zendesk.client.v2.model.Request.class, "requests")); } public Iterable<org.zendesk.client.v2.model.Request> getOpenRequests() { return new PagedIterable<org.zendesk.client.v2.model.Request>(cnst("/requests/open.json"), handleList(org.zendesk.client.v2.model.Request.class, "requests")); } public Iterable<org.zendesk.client.v2.model.Request> getSolvedRequests() { return new PagedIterable<org.zendesk.client.v2.model.Request>(cnst("/requests/solved.json"), handleList(org.zendesk.client.v2.model.Request.class, "requests")); } public Iterable<org.zendesk.client.v2.model.Request> getCCRequests() { return new PagedIterable<org.zendesk.client.v2.model.Request>(cnst("/requests/ccd.json"), handleList(org.zendesk.client.v2.model.Request.class, "requests")); } public Iterable<org.zendesk.client.v2.model.Request> getUserRequests(User user) { checkHasId(user); return getUserRequests(user.getId()); } public Iterable<org.zendesk.client.v2.model.Request> getUserRequests(long id) { return new PagedIterable<org.zendesk.client.v2.model.Request>(tmpl("/users/{id}/requests.json").set("id", id), handleList(org.zendesk.client.v2.model.Request.class, "requests")); } public org.zendesk.client.v2.model.Request getRequest(long id) { return complete(submit(req("GET", tmpl("/requests/{id}.json").set("id", id)), handle(org.zendesk.client.v2.model.Request.class, "request"))); } public org.zendesk.client.v2.model.Request createRequest(org.zendesk.client.v2.model.Request request) { return complete(submit(req("POST", cnst("/requests.json"), JSON, json(Collections.singletonMap("request", request))), handle(org.zendesk.client.v2.model.Request.class, "request"))); } public org.zendesk.client.v2.model.Request updateRequest(org.zendesk.client.v2.model.Request request) { checkHasId(request); return complete(submit(req("PUT", tmpl("/requests/{id}.json").set("id", request.getId()), JSON, json(Collections.singletonMap("request", request))), handle(org.zendesk.client.v2.model.Request.class, "request"))); } public Iterable<Comment> getRequestComments(org.zendesk.client.v2.model.Request request) { checkHasId(request); return getRequestComments(request.getId()); } public Iterable<Comment> getRequestComments(long id) { return new PagedIterable<Comment>(tmpl("/requests/{id}/comments.json").set("id", id), handleList(Comment.class, "comments")); } public Iterable<Comment> getTicketComments(long id) { return new PagedIterable<Comment>(tmpl("/tickets/{id}/comments.json").set("id", id), handleList(Comment.class, "comments")); } public Comment getRequestComment(org.zendesk.client.v2.model.Request request, Comment comment) { checkHasId(comment); return getRequestComment(request, comment.getId()); } public Comment getRequestComment(org.zendesk.client.v2.model.Request request, long commentId) { checkHasId(request); return getRequestComment(request.getId(), commentId); } public Comment getRequestComment(long requestId, long commentId) { return complete(submit(req("GET", tmpl("/requests/{requestId}/comments/{commentId}.json") .set("requestId", requestId) .set("commentId", commentId)), handle(Comment.class, "comment"))); } public Ticket createComment(long ticketId, Comment comment) { Ticket ticket = new Ticket(); ticket.setComment(comment); return complete(submit(req("PUT", tmpl("/tickets/{id}.json").set("id", ticketId), JSON, json(Collections.singletonMap("ticket", ticket))), handle(Ticket.class, "ticket"))); } public Ticket createTicketFromTweet(long tweetId, long monitorId) { Map<String,Object> map = new HashMap<String,Object>(); map.put("twitter_status_message_id", tweetId); map.put("monitored_twitter_handle_id", monitorId); return complete(submit(req("POST", cnst("/channels/twitter/tickets.json"), JSON, json(Collections.singletonMap("ticket", map))), handle(Ticket.class, "ticket"))); } public Iterable<Organization> getOrganizations() { return new PagedIterable<Organization>(cnst("/organizations.json"), handleList(Organization.class, "organizations")); } public Iterable<Organization> getOrganizationsIncrementally(Date startTime) { return new PagedIterable<Organization>( tmpl("/incremental/organizations.json{?start_time}").set("start_time", msToSeconds(startTime.getTime())), handleIncrementalList(Organization.class, "organizations")); } public Iterable<OrganizationField> getOrganizationFields() { //The organization_fields api doesn't seem to support paging return complete(submit(req("GET", cnst("/organization_fields.json")), handleList(OrganizationField.class, "organization_fields"))); } public Iterable<Organization> getAutoCompleteOrganizations(String name) { if (name == null || name.length() < 2) { throw new IllegalArgumentException("Name must be at least 2 characters long"); } return new PagedIterable<Organization>(tmpl("/organizations/autocomplete.json{?name}").set("name", name), handleList(Organization.class, "organizations")); } // TODO getOrganizationRelatedInformation public Organization getOrganization(long id) { return complete(submit(req("GET", tmpl("/organizations/{id}.json").set("id", id)), handle(Organization.class, "organization"))); } public Organization createOrganization(Organization organization) { return complete(submit(req("POST", cnst("/organizations.json"), JSON, json( Collections.singletonMap("organization", organization))), handle(Organization.class, "organization"))); } public JobStatus<Organization> createOrganizations(Organization... organizations) { return createOrganizations(Arrays.asList(organizations)); } public JobStatus createOrganizations(List<Organization> organizations) { return complete(createOrganizationsAsync(organizations)); } public ListenableFuture<JobStatus<Organization>> createOrganizationsAsync(List<Organization> organizations) { return submit(req("POST", cnst("/organizations/create_many.json"), JSON, json( Collections.singletonMap("organizations", organizations))), handleJobStatus(Organization.class)); } public Organization updateOrganization(Organization organization) { checkHasId(organization); return complete(submit(req("PUT", tmpl("/organizations/{id}.json").set("id", organization.getId()), JSON, json( Collections.singletonMap("organization", organization))), handle(Organization.class, "organization"))); } public void deleteOrganization(Organization organization) { checkHasId(organization); deleteOrganization(organization.getId()); } public void deleteOrganization(long id) { complete(submit(req("DELETE", tmpl("/organizations/{id}.json").set("id", id)), handleStatus())); } public Iterable<Organization> lookupOrganizationsByExternalId(String externalId) { if (externalId == null || externalId.length() < 2) { throw new IllegalArgumentException("Name must be at least 2 characters long"); } return new PagedIterable<Organization>( tmpl("/organizations/search.json{?external_id}").set("external_id", externalId), handleList(Organization.class, "organizations")); } public Iterable<OrganizationMembership> getOrganizationMemberships() { return new PagedIterable<OrganizationMembership>(cnst("/organization_memberships.json"), handleList(OrganizationMembership.class, "organization_memberships")); } public Iterable<OrganizationMembership> getOrganizationMembershipsForOrg(long organization_id) { return new PagedIterable<OrganizationMembership>(tmpl("/organizations/{organization_id}/organization_memberships.json").set("organization_id", organization_id), handleList(OrganizationMembership.class, "organization_memberships")); } public Iterable<OrganizationMembership> getOrganizationMembershipsForUser(long user_id) { return new PagedIterable<OrganizationMembership>(tmpl("/users/{user_id}/organization_memberships.json").set("user_id", user_id), handleList(OrganizationMembership.class, "organization_memberships")); } public OrganizationMembership getOrganizationMembershipForUser(long user_id, long id) { return complete(submit(req("GET", tmpl("/users/{user_id}/organization_memberships/{id}.json").set("user_id", user_id).set("id", id)), handle(OrganizationMembership.class, "organization_membership"))); } public OrganizationMembership getOrganizationMembership(long id) { return complete(submit(req("GET", tmpl("/organization_memberships/{id}.json").set("id", id)), handle(OrganizationMembership.class, "organization_membership"))); } public OrganizationMembership createOrganizationMembership(OrganizationMembership organizationMembership) { return complete(submit(req("POST", cnst("/organization_memberships.json"), JSON, json( Collections.singletonMap("organization_membership", organizationMembership))), handle(OrganizationMembership.class, "organization_membership"))); } public void deleteOrganizationMembership(long id) { complete(submit(req("DELETE", tmpl("/organization_memberships/{id}.json").set("id", id)), handleStatus())); } public Iterable<Group> getGroups() { return new PagedIterable<Group>(cnst("/groups.json"), handleList(Group.class, "groups")); } public Iterable<Group> getAssignableGroups() { return new PagedIterable<Group>(cnst("/groups/assignable.json"), handleList(Group.class, "groups")); } public Group getGroup(long id) { return complete(submit(req("GET", tmpl("/groups/{id}.json").set("id", id)), handle(Group.class, "group"))); } public Group createGroup(Group group) { return complete(submit(req("POST", cnst("/groups.json"), JSON, json( Collections.singletonMap("group", group))), handle(Group.class, "group"))); } /** * This API will be removed in a future release. The API endpoint does not exist. * Instead, the {@link #createGroup(Group) createGroup} method should be called for each Group * * @see <a href="https://github.com/cloudbees/zendesk-java-client/issues/111">Zendesk Java Client Issue #111</a> */ @Deprecated public List<Group> createGroups(Group... groups) { return createGroups(Arrays.asList(groups)); } /** * This API will be removed in a future release. The API endpoint does not exist. * Instead, the {@link #createGroup(Group) createGroup} method should be called for each Group * * @see <a href="https://github.com/cloudbees/zendesk-java-client/issues/111">Zendesk Java Client Issue #111</a> */ @Deprecated public List<Group> createGroups(List<Group> groups) { throw new ZendeskException("API Endpoint for createGroups does not exist."); } public Group updateGroup(Group group) { checkHasId(group); return complete(submit(req("PUT", tmpl("/groups/{id}.json").set("id", group.getId()), JSON, json( Collections.singletonMap("group", group))), handle(Group.class, "group"))); } public void deleteGroup(Group group) { checkHasId(group); deleteGroup(group.getId()); } public void deleteGroup(long id) { complete(submit(req("DELETE", tmpl("/groups/{id}.json").set("id", id)), handleStatus())); } public Iterable<Macro> getMacros(){ return new PagedIterable<Macro>(cnst("/macros.json"), handleList(Macro.class, "macros")); } public Macro getMacro(long macroId){ return complete(submit(req("GET", tmpl("/macros/{id}.json").set("id", macroId)), handle(Macro.class, "macro"))); } public Macro createMacro(Macro macro) { return complete(submit( req("POST", cnst("/macros.json"), JSON, json(Collections.singletonMap("macro", macro))), handle(Macro.class, "macro"))); } public Macro updateMacro(Long macroId, Macro macro) { return complete(submit(req("PUT", tmpl("/macros/{id}.json").set("id", macroId), JSON, json(Collections.singletonMap("macro", macro))), handle(Macro.class, "macro"))); } public Ticket macrosShowChangesToTicket(long macroId) { return complete(submit(req("GET", tmpl("/macros/{id}/apply.json").set("id", macroId)), handle(TicketResult.class, "result"))).getTicket(); } public Ticket macrosShowTicketAfterChanges(long ticketId, long macroId) { return complete(submit(req("GET", tmpl("/tickets/{ticket_id}/macros/{id}/apply.json") .set("ticket_id", ticketId) .set("id", macroId)), handle(TicketResult.class, "result"))).getTicket(); } public List<String> addTagToTicket(long id, String... tags) { return complete(submit( req("PUT", tmpl("/tickets/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public List<String> addTagToTopics(long id, String... tags) { return complete(submit( req("PUT", tmpl("/topics/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public List<String> addTagToOrganisations(long id, String... tags) { return complete(submit( req("PUT", tmpl("/organizations/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public List<String> setTagOnTicket(long id, String... tags) { return complete(submit( req("POST", tmpl("/tickets/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public List<String> setTagOnTopics(long id, String... tags) { return complete(submit( req("POST", tmpl("/topics/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public List<String> setTagOnOrganisations(long id, String... tags) { return complete(submit( req("POST", tmpl("/organizations/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public List<String> removeTagFromTicket(long id, String... tags) { return complete(submit( req("DELETE", tmpl("/tickets/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public List<String> removeTagFromTopics(long id, String... tags) { return complete(submit( req("DELETE", tmpl("/topics/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public List<String> removeTagFromOrganisations(long id, String... tags) { return complete(submit( req("DELETE", tmpl("/organizations/{id}/tags.json").set("id", id), JSON, json(Collections.singletonMap("tags", tags))), handle(List.class, "tags"))); } public Map getIncrementalTicketsResult(long unixEpochTime) { return complete(submit( req("GET", tmpl("/exports/tickets.json?start_time={time}").set( "time", unixEpochTime)), handle(Map.class))); } public Iterable<GroupMembership> getGroupMemberships() { return new PagedIterable<GroupMembership>(cnst("/group_memberships.json"), handleList(GroupMembership.class, "group_memberships")); } public List<GroupMembership> getGroupMembershipByUser(long user_id) { return complete(submit(req("GET", tmpl("/users/{user_id}/group_memberships.json").set("user_id", user_id)), handleList(GroupMembership.class, "group_memberships"))); } public List<GroupMembership> getGroupMemberships(long group_id) { return complete(submit(req("GET", tmpl("/groups/{group_id}/memberships.json").set("group_id", group_id)), handleList(GroupMembership.class, "group_memberships"))); } public Iterable<GroupMembership> getAssignableGroupMemberships() { return new PagedIterable<GroupMembership>(cnst("/group_memberships/assignable.json"), handleList(GroupMembership.class, "group_memberships")); } public List<GroupMembership> getAssignableGroupMemberships(long group_id) { return complete(submit(req("GET", tmpl("/groups/{group_id}/memberships/assignable.json").set("group_id", group_id)), handleList(GroupMembership.class, "group_memberships"))); } public GroupMembership getGroupMembership(long id) { return complete(submit(req("GET", tmpl("/group_memberships/{id}.json").set("id", id)), handle(GroupMembership.class, "group_membership"))); } public GroupMembership getGroupMembership(long user_id, long group_membership_id) { return complete(submit(req("GET", tmpl("/users/{uid}/group_memberships/{gmid}.json").set("uid", user_id) .set("gmid", group_membership_id)), handle(GroupMembership.class, "group_membership"))); } public GroupMembership createGroupMembership(GroupMembership groupMembership) { return complete(submit(req("POST", cnst("/group_memberships.json"), JSON, json( Collections.singletonMap("group_membership", groupMembership))), handle(GroupMembership.class, "group_membership"))); } public GroupMembership createGroupMembership(long user_id, GroupMembership groupMembership) { return complete(submit(req("POST", tmpl("/users/{id}/group_memberships.json").set("id", user_id), JSON, json(Collections.singletonMap("group_membership", groupMembership))), handle(GroupMembership.class, "group_membership"))); } public void deleteGroupMembership(GroupMembership groupMembership) { checkHasId(groupMembership); deleteGroupMembership(groupMembership.getId()); } public void deleteGroupMembership(long id) { complete(submit(req("DELETE", tmpl("/group_memberships/{id}.json").set("id", id)), handleStatus())); } public void deleteGroupMembership(long user_id, GroupMembership groupMembership) { checkHasId(groupMembership); deleteGroupMembership(user_id, groupMembership.getId()); } public void deleteGroupMembership(long user_id, long group_membership_id) { complete(submit(req("DELETE", tmpl("/users/{uid}/group_memberships/{gmid}.json").set("uid", user_id) .set("gmid", group_membership_id)), handleStatus())); } public List<GroupMembership> setGroupMembershipAsDefault(long user_id, GroupMembership groupMembership) { checkHasId(groupMembership); return complete(submit(req("PUT", tmpl("/users/{uid}/group_memberships/{gmid}/make_default.json") .set("uid", user_id).set("gmid", groupMembership.getId()), JSON, json( Collections.singletonMap("group_memberships", groupMembership))), handleList(GroupMembership.class, "results"))); } public Iterable<Forum> getForums() { return new PagedIterable<Forum>(cnst("/forums.json"), handleList(Forum.class, "forums")); } public List<Forum> getForums(long category_id) { return complete(submit(req("GET", tmpl("/categories/{id}/forums.json").set("id", category_id)), handleList(Forum.class, "forums"))); } public Forum getForum(long id) { return complete(submit(req("GET", tmpl("/forums/{id}.json").set("id", id)), handle(Forum.class, "forum"))); } public Forum createForum(Forum forum) { return complete(submit(req("POST", cnst("/forums.json"), JSON, json( Collections.singletonMap("forum", forum))), handle(Forum.class, "forum"))); } public Forum updateForum(Forum forum) { checkHasId(forum); return complete(submit(req("PUT", tmpl("/forums/{id}.json").set("id", forum.getId()), JSON, json( Collections.singletonMap("forum", forum))), handle(Forum.class, "forum"))); } public void deleteForum(Forum forum) { checkHasId(forum); complete(submit(req("DELETE", tmpl("/forums/{id}.json").set("id", forum.getId())), handleStatus())); } public Iterable<Topic> getTopics() { return new PagedIterable<Topic>(cnst("/topics.json"), handleList(Topic.class, "topics")); } public List<Topic> getTopics(long forum_id) { return complete(submit(req("GET", tmpl("/forums/{id}/topics.json").set("id", forum_id)), handleList(Topic.class, "topics"))); } public List<Topic> getTopicsByUser(long user_id) { return complete(submit(req("GET", tmpl("/users/{id}/topics.json").set("id", user_id)), handleList(Topic.class, "topics"))); } public Topic getTopic(long id) { return complete(submit(req("GET", tmpl("/topics/{id}.json").set("id", id)), handle(Topic.class, "topic"))); } public Topic createTopic(Topic topic) { checkHasId(topic); return complete(submit(req("POST", cnst("/topics.json"), JSON, json( Collections.singletonMap("topic", topic))), handle(Topic.class, "topic"))); } public Topic importTopic(Topic topic) { checkHasId(topic); return complete(submit(req("POST", cnst("/import/topics.json"), JSON, json( Collections.singletonMap("topic", topic))), handle(Topic.class, "topic"))); } public List<Topic> getTopics(long id, long... ids) { return complete(submit(req("POST", tmpl("/topics/show_many.json{?ids}").set("ids", idArray(id, ids))), handleList(Topic.class, "topics"))); } public Topic updateTopic(Topic topic) { checkHasId(topic); return complete(submit(req("PUT", tmpl("/topics/{id}.json").set("id", topic.getId()), JSON, json( Collections.singletonMap("topic", topic))), handle(Topic.class, "topic"))); } public void deleteTopic(Topic topic) { checkHasId(topic); complete(submit(req("DELETE", tmpl("/topics/{id}.json").set("id", topic.getId())), handleStatus())); } // https://support.zendesk.com/hc/communities/public/posts/203464106-Managing-Organization-Memberships-via-the-Zendesk-API public List<OrganizationMembership> getOrganizationMembershipByUser(long user_id) { return complete(submit(req("GET", tmpl("/users/{user_id}/organization_memberships.json").set("user_id", user_id)), handleList(OrganizationMembership.class, "organization_memberships"))); } public OrganizationMembership getGroupOrganization(long user_id, long organization_membership_id) { return complete(submit(req("GET", tmpl("/users/{uid}/organization_memberships/{oid}.json").set("uid", user_id) .set("oid", organization_membership_id)), handle(OrganizationMembership.class, "organization_membership"))); } public OrganizationMembership createOrganizationMembership(long user_id, OrganizationMembership organizationMembership) { return complete(submit(req("POST", tmpl("/users/{id}/organization_memberships.json").set("id", user_id), JSON, json(Collections.singletonMap("organization_membership", organizationMembership))), handle(OrganizationMembership.class, "organization_membership"))); } public void deleteOrganizationMembership(long user_id, OrganizationMembership organizationMembership) { checkHasId(organizationMembership); deleteOrganizationMembership(user_id, organizationMembership.getId()); } public void deleteOrganizationMembership(long user_id, long organization_membership_id) { complete(submit(req("DELETE", tmpl("/users/{uid}/organization_memberships/{oid}.json").set("uid", user_id) .set("oid", organization_membership_id)), handleStatus())); } public List<OrganizationMembership> setOrganizationMembershipAsDefault(long user_id, OrganizationMembership organizationMembership) { checkHasId(organizationMembership); return complete(submit(req("POST", tmpl("/users/{uid}/organization_memberships/{gmid}/make_default.json") .set("uid", user_id).set("gmid", organizationMembership.getId()), JSON, json( Collections.singletonMap("group_memberships", organizationMembership))), handleList(OrganizationMembership.class, "results"))); } //-- END BETA public Iterable<SearchResultEntity> getSearchResults(String query) { return new PagedIterable<SearchResultEntity>(tmpl("/search.json{?query}").set("query", query), handleSearchList("results")); } public <T extends SearchResultEntity> Iterable<T> getSearchResults(Class<T> type, String query) { return getSearchResults(type, query, null); } public <T extends SearchResultEntity> Iterable<T> getSearchResults(Class<T> type, String query, String params) { String typeName = null; for (Map.Entry<String, Class<? extends SearchResultEntity>> entry : searchResultTypes.entrySet()) { if (type.equals(entry.getValue())) { typeName = entry.getKey(); break; } } if (typeName == null) { return Collections.emptyList(); } return new PagedIterable<T>(tmpl("/search.json{?query,params}") .set("query", query + "+type:" + typeName) .set("params", params), handleList(type, "results")); } public void notifyApp(String json) { complete(submit(req("POST", cnst("/apps/notify.json"), JSON, json.getBytes()), handleStatus())); } public void updateInstallation(int id, String json) { complete(submit(req("PUT", tmpl("/apps/installations/{id}.json").set("id", id), JSON, json.getBytes()), handleStatus())); } public Iterable<SatisfactionRating> getSatisfactionRatings() { return new PagedIterable<SatisfactionRating>(cnst("/satisfaction_ratings.json"), handleList(SatisfactionRating.class, "satisfaction_ratings")); } public SatisfactionRating getSatisfactionRating(long id) { return complete(submit(req("GET", tmpl("/satisfaction_ratings/{id}.json").set("id", id)), handle(SatisfactionRating.class, "satisfaction_rating"))); } public SatisfactionRating createSatisfactionRating(long ticketId, SatisfactionRating satisfactionRating) { return complete(submit(req("POST", tmpl("/tickets/{ticketId}/satisfaction_rating.json") .set("ticketId", ticketId), JSON, json(Collections.singletonMap("satisfaction_rating", satisfactionRating))), handle(SatisfactionRating.class, "satisfaction_rating"))); } public SatisfactionRating createSatisfactionRating(Ticket ticket, SatisfactionRating satisfactionRating) { return createSatisfactionRating(ticket.getId(), satisfactionRating); } // TODO search with sort order // TODO search with query building API ////////////////////////////////////////////////////////////////////// // Action methods for Help Center ////////////////////////////////////////////////////////////////////// /** * Get all articles from help center. * * @return List of Articles. */ public Iterable<Article> getArticles() { return new PagedIterable<Article>(cnst("/help_center/articles.json"), handleList(Article.class, "articles")); } public Iterable<Article> getArticles(Category category) { checkHasId(category); return new PagedIterable<Article>( tmpl("/help_center/categories/{id}/articles.json").set("id", category.getId()), handleList(Article.class, "articles")); } public Iterable<Article> getArticlesIncrementally(Date startTime) { return new PagedIterable<Article>( tmpl("/help_center/incremental/articles.json{?start_time}") .set("start_time", msToSeconds(startTime.getTime())), handleIncrementalList(Article.class, "articles")); } public List<Article> getArticlesFromPage(int page) { return complete(submit(req("GET", tmpl("/help_center/articles.json?page={page}").set("page", page)), handleList(Article.class, "articles"))); } public Article getArticle(long id) { return complete(submit(req("GET", tmpl("/help_center/articles/{id}.json").set("id", id)), handle(Article.class, "article"))); } public Iterable<Translation> getArticleTranslations(Long articleId) { return new PagedIterable<Translation>( tmpl("/help_center/articles/{articleId}/translations.json").set("articleId", articleId), handleList(Translation.class, "translations")); } public Article createArticle(Article article) { checkHasSectionId(article); return complete(submit(req("POST", tmpl("/help_center/sections/{id}/articles.json").set("id", article.getSectionId()), JSON, json(Collections.singletonMap("article", article))), handle(Article.class, "article"))); } public Article updateArticle(Article article) { checkHasId(article); return complete(submit(req("PUT", tmpl("/help_center/articles/{id}.json").set("id", article.getId()), JSON, json(Collections.singletonMap("article", article))), handle(Article.class, "article"))); } public Translation updateArticleTranslation(Long articleId, String locale, Translation translation) { checkHasId(translation); return complete(submit(req("PUT", tmpl("/help_center/articles/{id}/translations/{locale}.json").set("id", articleId).set("locale",locale), JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation"))); } public void deleteArticle(Article article) { checkHasId(article); complete(submit(req("DELETE", tmpl("/help_center/articles/{id}.json").set("id", article.getId())), handleStatus())); } /** * Delete attachment from article. * @param attachment */ public void deleteArticleAttachment(ArticleAttachments attachment) { if (attachment.getId() == 0) { throw new IllegalArgumentException("Attachment requires id"); } deleteArticleAttachment(attachment.getId()); } /** * Delete attachment from article. * @param id attachment identifier. */ public void deleteArticleAttachment(long id) { complete(submit(req("DELETE", tmpl("/help_center/articles/attachments/{id}.json").set("id", id)), handleStatus())); } public Iterable<Category> getCategories() { return new PagedIterable<Category>(cnst("/help_center/categories.json"), handleList(Category.class, "categories")); } public Category getCategory(long id) { return complete(submit(req("GET", tmpl("/help_center/categories/{id}.json").set("id", id)), handle(Category.class, "category"))); } public Iterable<Translation> getCategoryTranslations(Long categoryId) { return new PagedIterable<Translation>( tmpl("/help_center/categories/{categoryId}/translations.json").set("categoryId", categoryId), handleList(Translation.class, "translations")); } public Category createCategory(Category category) { return complete(submit(req("POST", cnst("/help_center/categories.json"), JSON, json(Collections.singletonMap("category", category))), handle(Category.class, "category"))); } public Category updateCategory(Category category) { checkHasId(category); return complete(submit(req("PUT", tmpl("/help_center/categories/{id}.json").set("id", category.getId()), JSON, json(Collections.singletonMap("category", category))), handle(Category.class, "category"))); } public Translation updateCategoryTranslation(Long categoryId, String locale, Translation translation) { checkHasId(translation); return complete(submit(req("PUT", tmpl("/help_center/categories/{id}/translations/{locale}.json").set("id", categoryId).set("locale",locale), JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation"))); } public void deleteCategory(Category category) { checkHasId(category); complete(submit(req("DELETE", tmpl("/help_center/categories/{id}.json").set("id", category.getId())), handleStatus())); } public Iterable<Section> getSections() { return new PagedIterable<Section>( cnst("/help_center/sections.json"), handleList(Section.class, "sections")); } public Iterable<Section> getSections(Category category) { checkHasId(category); return new PagedIterable<Section>( tmpl("/help_center/categories/{id}/sections.json").set("id", category.getId()), handleList(Section.class, "sections")); } public Section getSection(long id) { return complete(submit(req("GET", tmpl("/help_center/sections/{id}.json").set("id", id)), handle(Section.class, "section"))); } public Iterable<Translation> getSectionTranslations(Long sectionId) { return new PagedIterable<Translation>( tmpl("/help_center/sections/{sectionId}/translations.json").set("sectionId", sectionId), handleList(Translation.class, "translations")); } public Section createSection(Section section) { return complete(submit(req("POST", cnst("/help_center/sections.json"), JSON, json(Collections.singletonMap("section", section))), handle(Section.class, "section"))); } public Section updateSection(Section section) { checkHasId(section); return complete(submit(req("PUT", tmpl("/help_center/sections/{id}.json").set("id", section.getId()), JSON, json(Collections.singletonMap("section", section))), handle(Section.class, "section"))); } public Translation updateSectionTranslation(Long sectionId, String locale, Translation translation) { checkHasId(translation); return complete(submit(req("PUT", tmpl("/help_center/sections/{id}/translations/{locale}.json").set("id", sectionId).set("locale",locale), JSON, json(Collections.singletonMap("translation", translation))), handle(Translation.class, "translation"))); } public void deleteSection(Section section) { checkHasId(section); complete(submit(req("DELETE", tmpl("/help_center/sections/{id}.json").set("id", section.getId())), handleStatus())); } public Iterable<Subscription> getUserSubscriptions(User user) { checkHasId(user); return getUserSubscriptions(user.getId()); } public Iterable<Subscription> getUserSubscriptions(Long userId) { return new PagedIterable<Subscription>( tmpl("/help_center/users/{userId}/subscriptions.json").set("userId", userId), handleList(Subscription.class, "subscriptions")); } public Iterable<Subscription> getArticleSubscriptions(Long articleId) { return getArticleSubscriptions(articleId, null); } public Iterable<Subscription> getArticleSubscriptions(Long articleId, String locale) { return new PagedIterable<Subscription>( tmpl("/help_center{/locale}/articles/{articleId}/subscriptions.json").set("locale", locale).set("articleId", articleId), handleList(Subscription.class, "subscriptions")); } public Iterable<Subscription> getSectionSubscriptions(Long sectionId) { return getSectionSubscriptions(sectionId, null); } public Iterable<Subscription> getSectionSubscriptions(Long sectionId, String locale) { return new PagedIterable<Subscription>( tmpl("/help_center{/locale}/sections/{sectionId}/subscriptions.json").set("locale", locale).set("sectionId", sectionId), handleList(Subscription.class, "subscriptions")); } /** * Get a list of the current business hours schedules * @return A List of Schedules */ public Iterable<Schedule> getSchedules() { return complete(submit(req("GET", cnst("/business_hours/schedules.json")), handleList(Schedule.class, "schedules"))); } public Schedule getSchedule(Schedule schedule) { checkHasId(schedule); return getSchedule(schedule.getId()); } public Schedule getSchedule(Long scheduleId) { return complete(submit(req("GET", tmpl("/business_hours/schedules/{id}.json").set("id", scheduleId)), handle(Schedule.class, "schedule"))); } public Iterable<Holiday> getHolidaysForSchedule(Schedule schedule) { checkHasId(schedule); return getHolidaysForSchedule(schedule.getId()); } public Iterable<Holiday> getHolidaysForSchedule(Long scheduleId) { return complete(submit(req("GET", tmpl("/business_hours/schedules/{id}/holidays.json").set("id", scheduleId)), handleList(Holiday.class, "holidays"))); } ////////////////////////////////////////////////////////////////////// // Helper methods ////////////////////////////////////////////////////////////////////// private byte[] json(Object object) { try { return mapper.writeValueAsBytes(object); } catch (JsonProcessingException e) { throw new ZendeskException(e.getMessage(), e); } } private <T> ListenableFuture<T> submit(Request request, ZendeskAsyncCompletionHandler<T> handler) { if (logger.isDebugEnabled()) { if (request.getStringData() != null) { logger.debug("Request {} {}\n{}", request.getMethod(), request.getUrl(), request.getStringData()); } else if (request.getByteData() != null) { logger.debug("Request {} {} {} {} bytes", request.getMethod(), request.getUrl(), request.getHeaders().getFirstValue("Content-type"), request.getByteData().length); } else { logger.debug("Request {} {}", request.getMethod(), request.getUrl()); } } return client.executeRequest(request, handler); } private static abstract class ZendeskAsyncCompletionHandler<T> extends AsyncCompletionHandler<T> { @Override public void onThrowable(Throwable t) { if (t instanceof IOException) { throw new ZendeskException(t); } else { super.onThrowable(t); } } } private Request req(String method, Uri template) { return req(method, template.toString()); } private static final Pattern RESTRICTED_PATTERN = Pattern.compile("%2B", Pattern.LITERAL); private Request req(String method, String url) { RequestBuilder builder = new RequestBuilder(method); if (realm != null) { builder.setRealm(realm); } else { builder.addHeader("Authorization", "Bearer " + oauthToken); } builder.setUrl(RESTRICTED_PATTERN.matcher(url).replaceAll("+")); // replace out %2B with + due to API restriction return builder.build(); } private Request req(String method, Uri template, String contentType, byte[] body) { RequestBuilder builder = new RequestBuilder(method); if (realm != null) { builder.setRealm(realm); } else { builder.addHeader("Authorization", "Bearer " + oauthToken); } builder.setUrl(RESTRICTED_PATTERN.matcher(template.toString()).replaceAll("+")); //replace out %2B with + due to API restriction builder.addHeader("Content-type", contentType); builder.setBody(body); return builder.build(); } protected ZendeskAsyncCompletionHandler<Void> handleStatus() { return new ZendeskAsyncCompletionHandler<Void>() { @Override public Void onCompleted(Response response) throws Exception { logResponse(response); if (isStatus2xx(response)) { return null; } else if (isRateLimitResponse(response)) { throw new ZendeskResponseRateLimitException(response); } throw new ZendeskResponseException(response); } }; } @SuppressWarnings("unchecked") protected <T> ZendeskAsyncCompletionHandler<T> handle(final Class<T> clazz) { return new ZendeskAsyncCompletionHandler<T>() { @Override public T onCompleted(Response response) throws Exception { logResponse(response); if (isStatus2xx(response)) { return (T) mapper.reader(clazz).readValue(response.getResponseBodyAsStream()); } else if (isRateLimitResponse(response)) { throw new ZendeskResponseRateLimitException(response); } if (response.getStatusCode() == 404) { return null; } throw new ZendeskResponseException(response); } }; } private class BasicAsyncCompletionHandler<T> extends ZendeskAsyncCompletionHandler<T> { private final Class<T> clazz; private final String name; private final Class[] typeParams; public BasicAsyncCompletionHandler(Class clazz, String name, Class... typeParams) { this.clazz = clazz; this.name = name; this.typeParams = typeParams; } @Override public T onCompleted(Response response) throws Exception { logResponse(response); if (isStatus2xx(response)) { if (typeParams.length > 0) { JavaType type = mapper.getTypeFactory().constructParametricType(clazz, typeParams); return mapper.convertValue(mapper.readTree(response.getResponseBodyAsStream()).get(name), type); } return mapper.convertValue(mapper.readTree(response.getResponseBodyAsStream()).get(name), clazz); } else if (isRateLimitResponse(response)) { throw new ZendeskResponseRateLimitException(response); } if (response.getStatusCode() == 404) { return null; } throw new ZendeskResponseException(response); } } protected <T> ZendeskAsyncCompletionHandler<T> handle(final Class<T> clazz, final String name, final Class... typeParams) { return new BasicAsyncCompletionHandler<T>(clazz, name, typeParams); } protected <T> ZendeskAsyncCompletionHandler<JobStatus<T>> handleJobStatus(final Class<T> resultClass) { return new BasicAsyncCompletionHandler<JobStatus<T>>(JobStatus.class, "job_status", resultClass) { @Override public JobStatus<T> onCompleted(Response response) throws Exception { JobStatus<T> result = super.onCompleted(response); result.setResultsClass(resultClass); return result; } }; } private static final String NEXT_PAGE = "next_page"; private static final String END_TIME = "end_time"; private static final String COUNT = "count"; private static final int INCREMENTAL_EXPORT_MAX_COUNT_BY_REQUEST = 1000; private abstract class PagedAsyncCompletionHandler<T> extends ZendeskAsyncCompletionHandler<T> { private String nextPage; public void setPagedProperties(JsonNode responseNode, Class<?> clazz) { JsonNode node = responseNode.get(NEXT_PAGE); if (node == null) { this.nextPage = null; if (logger.isDebugEnabled()) { logger.debug(NEXT_PAGE + " property not found, pagination not supported" + (clazz != null ? " for " + clazz.getName() : "")); } } else { this.nextPage = node.asText(); } } public String getNextPage() { return nextPage; } public void setNextPage(String nextPage) { this.nextPage = nextPage; } } private class PagedAsyncListCompletionHandler<T> extends PagedAsyncCompletionHandler<List<T>> { private final Class<T> clazz; private final String name; public PagedAsyncListCompletionHandler(Class<T> clazz, String name) { this.clazz = clazz; this.name = name; } @Override public List<T> onCompleted(Response response) throws Exception { logResponse(response); if (isStatus2xx(response)) { JsonNode responseNode = mapper.readTree(response.getResponseBodyAsBytes()); setPagedProperties(responseNode, clazz); List<T> values = new ArrayList<T>(); for (JsonNode node : responseNode.get(name)) { values.add(mapper.convertValue(node, clazz)); } return values; } else if (isRateLimitResponse(response)) { throw new ZendeskResponseRateLimitException(response); } throw new ZendeskResponseException(response); } } protected <T> PagedAsyncCompletionHandler<List<T>> handleList(final Class<T> clazz, final String name) { return new PagedAsyncListCompletionHandler<T>(clazz, name); } private static final long FIVE_MINUTES = TimeUnit.MINUTES.toMillis(5); protected <T> PagedAsyncCompletionHandler<List<T>> handleIncrementalList(final Class<T> clazz, final String name) { return new PagedAsyncListCompletionHandler<T>(clazz, name) { @Override public void setPagedProperties(JsonNode responseNode, Class<?> clazz) { JsonNode node = responseNode.get(NEXT_PAGE); if (node == null) { if (logger.isDebugEnabled()) { logger.debug(NEXT_PAGE + " property not found, pagination not supported" + (clazz != null ? " for " + clazz.getName() : "")); } setNextPage(null); return; } JsonNode endTimeNode = responseNode.get(END_TIME); if (endTimeNode == null || endTimeNode.asLong() == 0) { if (logger.isDebugEnabled()) { logger.debug(END_TIME + " property not found, incremental export pagination not supported" + (clazz != null ? " for " + clazz.getName() : "")); } setNextPage(null); return; } /** * A request after five minutes ago will result in a 422 responds from Zendesk. * Therefore, we stop pagination. */ if (TimeUnit.SECONDS.toMillis(endTimeNode.asLong()) > System.currentTimeMillis() - FIVE_MINUTES) { setNextPage(null); } else { // Taking into account documentation found at https://developer.zendesk.com/rest_api/docs/core/incremental_export#polling-strategy JsonNode countNode = responseNode.get(COUNT); if (countNode == null) { if (logger.isDebugEnabled()) { logger.debug(COUNT + " property not found, incremental export pagination not supported" + (clazz != null ? " for " + clazz.getName() : "")); } setNextPage(null); return; } if (countNode.asInt() < INCREMENTAL_EXPORT_MAX_COUNT_BY_REQUEST) { setNextPage(null); } else { setNextPage(node.asText()); } } } }; } protected PagedAsyncCompletionHandler<List<SearchResultEntity>> handleSearchList(final String name) { return new PagedAsyncCompletionHandler<List<SearchResultEntity>>() { @Override public List<SearchResultEntity> onCompleted(Response response) throws Exception { logResponse(response); if (isStatus2xx(response)) { JsonNode responseNode = mapper.readTree(response.getResponseBodyAsStream()).get(name); setPagedProperties(responseNode, null); List<SearchResultEntity> values = new ArrayList<SearchResultEntity>(); for (JsonNode node : responseNode) { Class<? extends SearchResultEntity> clazz = searchResultTypes.get(node.get("result_type").asText()); if (clazz != null) { values.add(mapper.convertValue(node, clazz)); } } return values; } else if (isRateLimitResponse(response)) { throw new ZendeskResponseRateLimitException(response); } throw new ZendeskResponseException(response); } }; } protected PagedAsyncCompletionHandler<List<Target>> handleTargetList(final String name) { return new PagedAsyncCompletionHandler<List<Target>>() { @Override public List<Target> onCompleted(Response response) throws Exception { logResponse(response); if (isStatus2xx(response)) { JsonNode responseNode = mapper.readTree(response.getResponseBodyAsBytes()); setPagedProperties(responseNode, null); List<Target> values = new ArrayList<Target>(); for (JsonNode node : responseNode.get(name)) { Class<? extends Target> clazz = targetTypes.get(node.get("type").asText()); if (clazz != null) { values.add(mapper.convertValue(node, clazz)); } } return values; } else if (isRateLimitResponse(response)) { throw new ZendeskResponseRateLimitException(response); } throw new ZendeskResponseException(response); } }; } protected PagedAsyncCompletionHandler<List<ArticleAttachments>> handleArticleAttachmentsList(final String name) { return new PagedAsyncCompletionHandler<List<ArticleAttachments>>() { @Override public List<ArticleAttachments> onCompleted(Response response) throws Exception { logResponse(response); if (isStatus2xx(response)) { JsonNode responseNode = mapper.readTree(response.getResponseBodyAsBytes()); List<ArticleAttachments> values = new ArrayList<ArticleAttachments>(); for (JsonNode node : responseNode.get(name)) { values.add(mapper.convertValue(node, ArticleAttachments.class)); } return values; } else if (isRateLimitResponse(response)) { throw new ZendeskResponseRateLimitException(response); } throw new ZendeskResponseException(response); } }; } private TemplateUri tmpl(String template) { return new TemplateUri(url + template); } private Uri cnst(String template) { return new FixedUri(url + template); } private void logResponse(Response response) throws IOException { if (logger.isDebugEnabled()) { logger.debug("Response HTTP/{} {}\n{}", response.getStatusCode(), response.getStatusText(), response.getResponseBody()); } if (logger.isTraceEnabled()) { logger.trace("Response headers {}", response.getHeaders()); } } private static final String UTF_8 = "UTF-8"; private static String encodeUrl(String input) { try { return URLEncoder.encode(input, UTF_8); } catch (UnsupportedEncodingException impossible) { return input; } } private static long msToSeconds(long millis) { return TimeUnit.MILLISECONDS.toSeconds(millis); } private boolean isStatus2xx(Response response) { return response.getStatusCode() / 100 == 2; } private boolean isRateLimitResponse(Response response) { return response.getStatusCode() == 429; } ////////////////////////////////////////////////////////////////////// // Static helper methods ////////////////////////////////////////////////////////////////////// private static <T> T complete(ListenableFuture<T> future) { try { return future.get(); } catch (InterruptedException e) { throw new ZendeskException(e.getMessage(), e); } catch (ExecutionException e) { if (e.getCause() instanceof ZendeskException) { throw (ZendeskException) e.getCause(); } throw new ZendeskException(e.getMessage(), e); } } private static void checkHasId(Ticket ticket) { if (ticket.getId() == null) { throw new IllegalArgumentException("Ticket requires id"); } } private static void checkHasId(org.zendesk.client.v2.model.Request request) { if (request.getId() == null) { throw new IllegalArgumentException("Request requires id"); } } private static void checkHasId(Audit audit) { if (audit.getId() == null) { throw new IllegalArgumentException("Audit requires id"); } } private static void checkHasId(Comment comment) { if (comment.getId() == null) { throw new IllegalArgumentException("Comment requires id"); } } private static void checkHasId(Field field) { if (field.getId() == null) { throw new IllegalArgumentException("Field requires id"); } } private static void checkHasId(Attachment attachment) { if (attachment.getId() == null) { throw new IllegalArgumentException("Attachment requires id"); } } private static void checkHasId(User user) { if (user.getId() == null) { throw new IllegalArgumentException("User requires id"); } } private static void checkHasId(Identity identity) { if (identity.getId() == null) { throw new IllegalArgumentException("Identity requires id"); } } private static void checkHasId(Organization organization) { if (organization.getId() == null) { throw new IllegalArgumentException("Organization requires id"); } } private static void checkHasId(Group group) { if (group.getId() == null) { throw new IllegalArgumentException("Group requires id"); } } private static void checkHasId(GroupMembership groupMembership) { if (groupMembership.getId() == null) { throw new IllegalArgumentException("GroupMembership requires id"); } } private void checkHasId(Forum forum) { if (forum.getId() == null) { throw new IllegalArgumentException("Forum requires id"); } } private void checkHasId(Topic topic) { if (topic.getId() == null) { throw new IllegalArgumentException("Topic requires id"); } } private static void checkHasId(OrganizationMembership organizationMembership) { if (organizationMembership.getId() == null) { throw new IllegalArgumentException("OrganizationMembership requires id"); } } private static void checkHasId(Article article) { if (article.getId() == null) { throw new IllegalArgumentException("Article requires id"); } } private static void checkHasSectionId(Article article) { if (article.getSectionId() == null) { throw new IllegalArgumentException("Article requires section id"); } } private static void checkHasId(Category category) { if (category.getId() == null) { throw new IllegalArgumentException("Category requires id"); } } private static void checkHasId(Section section) { if (section.getId() == null) { throw new IllegalArgumentException("Section requires id"); } } private static void checkHasId(SuspendedTicket ticket) { if (ticket == null || ticket.getId() == null) { throw new IllegalArgumentException("SuspendedTicket requires id"); } } private static void checkHasId(Translation translation) { if (translation.getId() == null) { throw new IllegalArgumentException("Translation requires id"); } } private static void checkHasId(Schedule schedule) { if (schedule == null || schedule.getId() == null) { throw new IllegalArgumentException("Schedule requires id"); } } private static void checkHasId(Holiday holiday) { if (holiday == null || holiday.getId() == null) { throw new IllegalArgumentException("Holiday requires id"); } } private static void checkHasToken(Attachment.Upload upload) { if (upload.getToken() == null) { throw new IllegalArgumentException("Upload requires token"); } } private static List<Long> idArray(long id, long... ids) { List<Long> result = new ArrayList<Long>(ids.length + 1); result.add(id); for (long i : ids) { result.add(i); } return result; } private static List<String> statusArray(Status... statuses) { List<String> result = new ArrayList<String>(statuses.length); for (Status s : statuses) { result.add(s.toString()); } return result; } public static ObjectMapper createMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper; } ////////////////////////////////////////////////////////////////////// // Helper classes ////////////////////////////////////////////////////////////////////// private class PagedIterable<T> implements Iterable<T> { private final Uri url; private final PagedAsyncCompletionHandler<List<T>> handler; private PagedIterable(Uri url, PagedAsyncCompletionHandler<List<T>> handler) { this.handler = handler; this.url = url; } public Iterator<T> iterator() { return new PagedIterator(url); } private class PagedIterator implements Iterator<T> { private Iterator<T> current; private String nextPage; public PagedIterator(Uri url) { this.nextPage = url.toString(); } public boolean hasNext() { if (current == null || !current.hasNext()) { if (nextPage == null || nextPage.equalsIgnoreCase("null")) { return false; } List<T> values = complete(submit(req("GET", nextPage), handler)); nextPage = handler.getNextPage(); current = values.iterator(); } return current.hasNext(); } public T next() { if (!hasNext()) { throw new NoSuchElementException(); } return current.next(); } public void remove() { throw new UnsupportedOperationException(); } } } public static class Builder { private AsyncHttpClient client = null; private final String url; private String username = null; private String password = null; private String token = null; private String oauthToken = null; public Builder(String url) { this.url = url; } public Builder setClient(AsyncHttpClient client) { this.client = client; return this; } public Builder setUsername(String username) { this.username = username; return this; } public Builder setPassword(String password) { this.password = password; if (password != null) { this.token = null; this.oauthToken = null; } return this; } public Builder setToken(String token) { this.token = token; if (token != null) { this.password = null; this.oauthToken = null; } return this; } public Builder setOauthToken(String oauthToken) { this.oauthToken = oauthToken; if (oauthToken != null) { this.password = null; this.token = null; } return this; } public Builder setRetry(boolean retry) { return this; } public Zendesk build() { if (token != null) { return new Zendesk(client, url, username + "/token", token); } else if (oauthToken != null) { return new Zendesk(client, url, oauthToken); } return new Zendesk(client, url, username, password); } } }